package org.dicr.gui.data.sql;

import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.util.*;

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.table.*;

import org.dicr.util.jds.*;

import com.borland.dbswing.*;
import com.borland.dx.dataset.*;

/***********************************************************************************************************************
 * @author Igor A Tarasov me@dicr.dn.ua
 * @version 021228
 **********************************************************************************************************************/
public class JdbRowTable extends JTable implements DataSetAware {
	private static final long serialVersionUID = 1L;

	DetailTableModel model = new DetailTableModel();

	private DetailRowHeaderRenderer rowHeaderRenderer = new DetailRowHeaderRenderer();

	private TableRowHeader rowHeader = new TableRowHeader(this, this.rowHeaderRenderer);

	private DetailCellRenderer cellRenderer = new DetailCellRenderer();

	DetailCellEditor cellEditor = new DetailCellEditor();

	/*******************************************************************************************************************
	 * Конструктор
	 ******************************************************************************************************************/
	public JdbRowTable() {
		jbInit();
	}

	/*******************************************************************************************************************
	 * Инициализация
	 ******************************************************************************************************************/
	private void jbInit() {
		// модель
		this.setModel(this.model);
		// марафет
		this.setAutoscrolls(true);
		this.setRowHeight(new JTextField("W").getPreferredSize().height);
		this.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
		this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		this.setColumnSelectionAllowed(false);
		this.setRowSelectionAllowed(true);
		// переменные
		this.rowHeader.setTable(this);
		this.rowHeader.setCellRenderer(this.rowHeaderRenderer);
	}

	/*******************************************************************************************************************
	 * Устанавливает включающей таблицу ScrollPane свой rowHeader
	 ******************************************************************************************************************/
	private void setScrollRowHeader(Component theRowHeader) {
		Container p = getParent();
		if (p instanceof JViewport) {
			Container gp = p.getParent();
			if (gp instanceof JScrollPane) {
				JScrollPane scrollPane = (JScrollPane) gp;
				JViewport viewport = scrollPane.getViewport();
				if (viewport != null && viewport.getView() == this) scrollPane.setRowHeaderView(theRowHeader);
			}
		}
	}

	/**
	 * @see javax.swing.JTable#configureEnclosingScrollPane()
	 */
	@Override
	protected void configureEnclosingScrollPane() {
		super.configureEnclosingScrollPane();
		setScrollRowHeader(this.rowHeader);
	}

	/**
	 * @see javax.swing.JTable#unconfigureEnclosingScrollPane()
	 */
	@Override
	protected void unconfigureEnclosingScrollPane() {
		super.unconfigureEnclosingScrollPane();
		setScrollRowHeader(null);
	}

	/*******************************************************************************************************************
	 * Установка источника данных
	 * 
	 * @param dataset
	 ******************************************************************************************************************/
	@Override
    public void setDataSet(DataSet dataset) {
		this.model.setDataSet(dataset);
	}

	/*******************************************************************************************************************
	 * Возвращает источник данных
	 * 
	 * @return .
	 ******************************************************************************************************************/
	@Override
    public DataSet getDataSet() {
		return this.model.getDataSet();
	}

	/*******************************************************************************************************************
	 * Устанавливает признак редактируемости
	 * 
	 * @param editable
	 ******************************************************************************************************************/
	public void setEditable(boolean editable) {
		this.cellEditor.setEditable(editable);
	}

	/**
	 * @see javax.swing.JTable#tableChanged(javax.swing.event.TableModelEvent)
	 */
	@Override
	public void tableChanged(TableModelEvent e) {
		super.tableChanged(e);
		if (this.model == null || e.getFirstRow() != TableModelEvent.HEADER_ROW) return;
		TableColumn column = this.getColumnModel().getColumn(0);
		if (column == null) return;
		column.setCellRenderer(this.cellRenderer);
		column.setCellEditor(this.cellEditor);
	}

	/**
	 * Модель данных таблицы
	 */
	class DetailTableModel extends AbstractTableModel implements DataChangeListener, NavigationListener, AccessListener, DataSetAware {
		private static final long serialVersionUID = 1L;

		private DataSet dataset = null;

		private Column[] visibleRows = null;

		private Column[] lookupColumns = null;

		private boolean dataSetEventEnabled = true;

		private boolean propertyChange = false;

		/***************************************************************************************************************
		 * Возвращает число строк
		 * 
		 * @return .
		 **************************************************************************************************************/
		@Override
        public int getRowCount() {
			return (this.visibleRows == null) ? 0 : this.visibleRows.length;
		}

		/**
		 * @see javax.swing.table.TableModel#getColumnCount()
		 */
		@Override
        public int getColumnCount() {
			return 1;
		}

		/**
		 * @see javax.swing.table.AbstractTableModel#getColumnName(int)
		 */
		@Override
		public String getColumnName(int columnIndex) {
			return null;
		}

		/**
		 * @param rowIndex
		 * @return row name
		 */
		public String getRowName(int rowIndex) {
			return this.visibleRows[rowIndex].getCaption();
		}

		/**
		 * @see javax.swing.table.AbstractTableModel#getColumnClass(int)
		 */
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			return Object.class;
		}

		/**
		 * @see javax.swing.table.AbstractTableModel#isCellEditable(int, int)
		 */
		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return DataUtils.isDataSetEditable(this.dataset)
			        && (this.dataset.inBounds() || this.dataset.isEditingNewRow())
			        && this.visibleRows[rowIndex].isEditable() && !this.visibleRows[rowIndex].isReadOnly()
			        && this.dataset.canSet(this.visibleRows[rowIndex]);
		}

		/**
		 * @see javax.swing.table.TableModel#getValueAt(int, int)
		 */
		@Override
        public Object getValueAt(int rowIndex, int columnIndex) {
			if (!isValidDataSetState()) return null;
			Variant value = new Variant();
			this.dataset.getDisplayVariant(this.visibleRows[rowIndex].getOrdinal(), this.dataset.getRow(), value);
			return value.getAsObject();
		}

		/**
		 * @see javax.swing.table.AbstractTableModel#setValueAt(java.lang.Object, int, int)
		 */
		@Override
		public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
			if (!isValidDataSetState()) return;
			this.dataset.setDisplayVariant(this.visibleRows[rowIndex].getOrdinal(), checkValue(aValue, rowIndex));
		}

		/***************************************************************************************************************
		 * Обновление модели таблицы
		 **************************************************************************************************************/
		protected void updateModel() {
			Vector<Column> v_rows = new Vector<Column>();
			Vector<Column> l_discol = new Vector<Column>();
			// обновляем структуру колонок модели даже при закрытом dataset
			if (this.dataset != null) try {
				Column[] dataCols = this.dataset.getColumns();
				for (int i = 0; i < dataCols.length; i++) {
					Column col = dataCols[i];
					if (this.dataset.columnIsVisible(col.getColumnName())) {
						Column lookupDisplayColumn = null;
						PickListDescriptor pickDescriptor = col.getPickList();
						if (pickDescriptor != null) {
							DataSet pickDataSet = pickDescriptor.getPickListDataSet();
							if (pickDataSet != null) {
								String pickName = pickDescriptor.getLookupDisplayColumn();
								if (pickName != null) lookupDisplayColumn = pickDataSet.hasColumn(pickName);
							}
						}
						v_rows.add(col);
						l_discol.add(lookupDisplayColumn);
					}
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				this.visibleRows = v_rows.toArray(new Column[0]);
				this.lookupColumns = l_discol.toArray(new Column[0]);
				fireTableChanged(new TableModelEvent(JdbRowTable.this.model, TableModelEvent.HEADER_ROW));
			}
		}

		/***************************************************************************************************************
		 * Установка источника данных
		 * 
		 * @param dataSet
		 **************************************************************************************************************/
		@Override
        public void setDataSet(DataSet dataSet) {
			if (this.dataset != dataSet) {
				if (this.dataset != null) {
					if (isEditing()) JdbRowTable.this.cellEditor.stopCellEditing();
					this.dataset.post();
					this.dataset.removeAccessListener(this);
					this.dataset.removeDataChangeListener(this);
					this.dataset.removeNavigationListener(this);
				}
				this.dataset = dataSet;
				if (this.dataset != null) try {
					this.dataset.addAccessListener(this);
					this.dataset.addDataChangeListener(this);
					this.dataset.addNavigationListener(this);
					dataSet.open();
				} finally {
					updateModel();
				}
			}
		}

		/***************************************************************************************************************
		 * Возвращает источник данных
		 * 
		 * @return .
		 **************************************************************************************************************/
		@Override
        public DataSet getDataSet() {
			return this.dataset;
		}

		/***************************************************************************************************************
		 * Проверка источника данных
		 * 
		 * @return true if valid
		 **************************************************************************************************************/
		protected boolean isValidDataSetState() {
			return this.dataset != null && this.dataset.isOpen();
		}

		/***************************************************************************************************************
		 * Возвращает тип отображаемого значения
		 * 
		 * @param row row number
		 * @return display type
		 **************************************************************************************************************/
		protected int getDisplayType(int row) {
			Column col = (this.lookupColumns[row] != null) ? this.lookupColumns[row] : this.visibleRows[row];
			return col.getDataType();
		}

		/***************************************************************************************************************
		 * Преобразует ОТОБРАЖАЕМОЕ значение из типа Object в Variant
		 * 
		 * @param aValue value of cell
		 * @param row row number
		 * @return variant type
		 **************************************************************************************************************/
		protected Variant checkValue(Object aValue, int row) {
			if (aValue == null) {
				return new Variant(Variant.ASSIGNED_NULL);
			} else if (aValue instanceof Variant) { return (Variant) aValue; }
			Variant value = new Variant();
			int type = getDisplayType(row);
			if (aValue instanceof String) {
				value.setFromString(type, (String) aValue);
			} else {
				value.setAsObject(aValue, type);
			}
			return value;
		}

		/***************************************************************************************************************
		 * Форматирует ОТОБРАЖАЕМОЕ значение в строку
		 * 
		 * @param value
		 * @param row
		 * @return .
		 **************************************************************************************************************/
		public String formatValue(Variant value, int row) {
			Column col = (this.lookupColumns[row] != null) ? this.lookupColumns[row] : this.visibleRows[row];
			return col.format(value);
		}

		/***************************************************************************************************************
		 * Обработка событий доступа к данным
		 * 
		 * @param event
		 **************************************************************************************************************/
		@Override
        public void accessChange(AccessEvent event) {
			if (event.getID() == AccessEvent.CLOSE) {
				if (event.getReason() == AccessEvent.STRUCTURE_CHANGE) {
					this.dataSetEventEnabled = false;
				} else if (event.getReason() == AccessEvent.UNKNOWN) {
					fireTableStructureChanged();
					fireTableDataChanged();
				} else if (event.getReason() == AccessEvent.PROPERTY_CHANGE) {
					this.propertyChange = true;
				}
			} else {
				this.dataSetEventEnabled = true;
				switch (event.getReason()) {
					case AccessEvent.UNSPECIFIED:
						updateModel();
						fireTableStructureChanged();
						if (!this.propertyChange) {
							fireTableDataChanged();
						}
						this.propertyChange = false;
						break;
					case AccessEvent.COLUMN_ADD:
					case AccessEvent.COLUMN_DROP:
					case AccessEvent.COLUMN_CHANGE:
					case AccessEvent.COLUMN_MOVE:
						updateModel();
						fireTableStructureChanged();
						break;
					case AccessEvent.DATA_CHANGE:
						this.dataSetEventEnabled = true;
						fireTableDataChanged();
						break;

				}
			}
		}

		/**
		 * @see com.borland.dx.dataset.DataChangeListener#dataChanged(com.borland.dx.dataset.DataChangeEvent)
		 */
		@Override
        public void dataChanged(DataChangeEvent event) {
			if (!this.dataSetEventEnabled) { return; }
			if (this.dataset.getRow() != event.getRowAffected()) { return; }
			fireTableDataChanged();
		}

		/**
		 * @see com.borland.dx.dataset.DataChangeListener#postRow(com.borland.dx.dataset.DataChangeEvent)
		 */
		@Override
        public void postRow(DataChangeEvent event) throws Exception {
		// nop
		}

		/**
		 * @see com.borland.dx.dataset.NavigationListener#navigated(com.borland.dx.dataset.NavigationEvent)
		 */
		@Override
        public void navigated(NavigationEvent event) {
			if (isEditing() && !JdbRowTable.this.cellEditor.stopCellEditing()) {
				JdbRowTable.this.cellEditor.cancelCellEditing();
			}
			fireTableDataChanged();
		}

		/***************************************************************************************************************
		 * Возвращает колонку из dataset в позиции rowIndex
		 * 
		 * @param rowIndex
		 * @return .
		 **************************************************************************************************************/
		public Column getVisibleRow(int rowIndex) {
			return this.visibleRows[rowIndex];
		}

		/***************************************************************************************************************
		 * Возвращает колонку из lookup dataset если колонка в rowIndex имеет lookup
		 * 
		 * @param rowIndex
		 * @return .
		 **************************************************************************************************************/
		public Column getLookupColumn(int rowIndex) {
			return this.lookupColumns[rowIndex];
		}
	}

	/*******************************************************************************************************************
	 * ######################################################################### Рисовалка заголовка строк
	 * #########################################################################
	 ******************************************************************************************************************/
	class DetailRowHeaderRenderer extends DefaultTableCellRenderer {
		private static final long serialVersionUID = 1L;

		private Font normalFont = null;

		private Font requredFont = null;

		private boolean inited = false;

		/***************************************************************************************************************
		 * Конструктор
		 **************************************************************************************************************/
		public DetailRowHeaderRenderer() {
			try {
				jbInit();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}

		/***************************************************************************************************************
		 * Инициализация
		 **************************************************************************************************************/
		private void jbInit() {
			setHorizontalAlignment(SwingConstants.LEFT);
			setBorder(new CompoundBorder(new EtchedBorder(EtchedBorder.RAISED), new EmptyBorder(2, 2, 2, 2)));
		}

		/**
		 * @see javax.swing.table.DefaultTableCellRenderer#getTableCellRendererComponent(javax.swing.JTable,
		 *      java.lang.Object, boolean, boolean, int, int)
		 */
		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
			if (!this.inited) {
				JTableHeader header = table.getTableHeader();
				if (header != null) {
					Font hf = header.getFont();
					this.normalFont = new Font(hf.getName(), Font.PLAIN, hf.getSize());
					this.requredFont = new Font(hf.getName(), Font.BOLD, hf.getSize());
					setForeground(header.getForeground());
					setBackground(header.getBackground());
					this.inited = true;
				}
			}
			// super.getTableCellRendererComponent(table,value,isSelected,hasFocus,row,col);

			switch (col) {
				case TableRowHeader.MAX_SIZE:
				case TableRowHeader.MIN_SIZE:
				case TableRowHeader.PREF_SIZE:
					String maxstring = null;
					if (JdbRowTable.this.model != null) {
						for (int i = 0; i < JdbRowTable.this.model.getRowCount(); i++) {
							String string = JdbRowTable.this.model.getRowName(i);
							if (string != null && (maxstring == null || string.length() > maxstring.length())) {
								maxstring = string;
							}
						}
					}
					if (maxstring == null) maxstring = "Тестовая строка";
					setText(" " + maxstring + ": ");
					setFont(this.requredFont);
					break;
				default:
					Column visibleRow = JdbRowTable.this.model.getVisibleRow(row);
					setText((visibleRow.getCaption() == null) ? " " + row + " " : " " + visibleRow.getCaption() + ": ");
					setFont(visibleRow.isRequired() ? this.requredFont : this.normalFont);
					break;
			}
			return this;
		}
	}

	/**
	 * Рисовалка ячеек данных таблицы
	 */
	class DetailCellRenderer implements TableCellRenderer {
		private JLabel label = new JLabel();

		private JCheckBox checkBox = new JCheckBox();

		private JComponent renderer = null;

		private boolean inited = false;

		private Color normalForeground = null;

		private Color normalBackground = null;

		private Color selectedForeground = null;

		private Color selectedBackground = null;

		/***************************************************************************************************************
		 * Конструктор
		 **************************************************************************************************************/
		public DetailCellRenderer() {
			try {
				jbInit();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}

		/***************************************************************************************************************
		 * Инициализация
		 **************************************************************************************************************/
		private void jbInit() {
			this.label.setHorizontalAlignment(SwingConstants.LEFT);
			this.label.setOpaque(true);
			this.checkBox.setOpaque(true);
		}

		/**
		 * @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object,
		 *      boolean, boolean, int, int)
		 */
		@Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {

			Column column = JdbRowTable.this.model.getVisibleRow(row);

			Object painter = column.getItemPainter();
			if ((painter != null) && (painter instanceof TableCellRenderer)) { return ((TableCellRenderer) painter).getTableCellRendererComponent(
			        table, value, isSelected, hasFocus, row, col); }

			if (!this.inited) {
				Font font = table.getFont();
				this.label.setFont(font);
				this.checkBox.setFont(font);
				this.normalForeground = table.getForeground();
				this.selectedForeground = table.getSelectionForeground();
				this.normalBackground = table.getBackground();
				this.selectedBackground = table.getSelectionBackground();
				this.inited = true;
			}
			Variant variant = JdbRowTable.this.model.checkValue(value, row);
			int type = variant.getType();
			if (type == Variant.BOOLEAN) {
				this.checkBox.setSelected(variant.isNull() ? false : variant.getBoolean());
				this.renderer = this.checkBox;
			} else {
				if (variant.isNull()) {
					this.label.setText("");
				} else if (type == Variant.TIME) {
					DateFormat format = DateFormat.getTimeInstance();
					this.label.setText(format.format(variant.getTime()));
				} else if (type == Variant.DATE) {
					DateFormat format = DateFormat.getDateInstance();
					this.label.setText(format.format(variant.getDate()));
				} else if (type == Variant.TIMESTAMP) {
					DateFormat format = DateFormat.getDateTimeInstance();
					this.label.setText(format.format(variant.getTimestamp()));
				} else {
					this.label.setText(JdbRowTable.this.model.formatValue(variant, row));
				}
				this.renderer = this.label;
			}
			if (isSelected) {
				this.renderer.setForeground(this.selectedForeground);
				this.renderer.setBackground(this.selectedBackground);
			} else {
				this.renderer.setForeground(this.normalForeground);
				this.renderer.setBackground(this.normalBackground);
			}
			return this.renderer;
		}
	}

	/*******************************************************************************************************************
	 * ######################################################################### Редактор ячеек таблицы
	 * #########################################################################
	 ******************************************************************************************************************/
	class DetailCellEditor implements TableCellEditor {
		private JComboBox comboBox = null;

		private JTextField textField = null;

		private JCheckBox checkBox = null;

		private JComponent editor = null;

		private int dataType = 0;

		boolean ignoreActions = false;

		private boolean editable = true;

		private Vector<CellEditorListener> listeners = new Vector<CellEditorListener>();

		private ActionListener actionListener = new ActionListener() {
			@Override
            public void actionPerformed(ActionEvent e) {
				if (!DetailCellEditor.this.ignoreActions) stopCellEditing();
			}
		};

		/**
		 * @see javax.swing.table.TableCellEditor#getTableCellEditorComponent(javax.swing.JTable, java.lang.Object,
		 *      boolean, int, int)
		 */
		@Override
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int rowIndex, int columnIndex) {

			// получаем колонку данных
			Column row = JdbRowTable.this.model.getVisibleRow(rowIndex);
			this.dataType = row.getDataType();
			// создаем компонент
			if (JdbRowTable.this.model.getLookupColumn(rowIndex) != null) {
				this.comboBox = new JComboBox();
				Column col = JdbRowTable.this.model.getVisibleRow(rowIndex);
				PickListDescriptor pick = col.getPickList();
				DataSet ds = pick.getPickListDataSet();
				String colname = pick.getPickListDisplayColumns()[0];
				DataRow dr = new DataRow(ds, colname);
				for (int i = 0; i < ds.getRowCount(); i++) {
					Variant v = new Variant();
					ds.getDataRow(i, dr);
					dr.getVariant(colname, v);
					this.comboBox.addItem(v.getAsObject());
				}
				this.comboBox.addActionListener(this.actionListener);
				this.editor = this.comboBox;
			} else if (this.dataType == Variant.BOOLEAN) {
				this.checkBox = new JCheckBox();
				this.checkBox.addActionListener(this.actionListener);
				this.editor = this.checkBox;
			} else {
				this.textField = new JTextField();
				this.textField.addActionListener(this.actionListener);
				this.editor = this.textField;
			}
			this.editor.setFont(table.getFont());
			// устанавливаем значение компонента
			setValue(value);
			return this.editor;
		}

		/**
		 * @see javax.swing.CellEditor#getCellEditorValue()
		 */
		@Override
        public Object getCellEditorValue() {
			Object value = null;
			if (this.editor == this.textField) {
				String text = this.textField.getText();
				if (text == null || text.length() < 1) {
					// value = null;
				} else if (this.dataType == Variant.TIME) {
					try {
						DateFormat format = DateFormat.getTimeInstance();
						value = new java.sql.Time(format.parse(text).getTime());
					} catch (Exception ex) {
						value = text;
					}
				} else if (this.dataType == Variant.DATE) {
					try {
						DateFormat format = DateFormat.getDateInstance();
						value = new java.sql.Date(format.parse(text).getTime());
					} catch (Exception ex) {
						value = text;
					}
				} else if (this.dataType == Variant.TIMESTAMP) {
					try {
						DateFormat format = DateFormat.getDateTimeInstance();
						value = new java.sql.Timestamp(format.parse(text).getTime());
					} catch (Exception ex) {
						value = text;
					}
				} else {
					value = text;
				}
			} else if (this.editor == this.checkBox) {
				value = Boolean.valueOf(this.checkBox.isSelected());
			} else if (this.editor == this.comboBox) {
				value = this.comboBox.getSelectedItem();
			}
			return value;
		}

		private void setValue(Object value) {
			try {
				this.ignoreActions = true;
				if (this.editor == this.textField) {
					String text = null;
					if (value == null) {
						text = "";
					} else {
						if (this.dataType == Variant.TIME) {
							DateFormat format = DateFormat.getTimeInstance();
							text = format.format(value);
						} else if (this.dataType == Variant.DATE) {
							DateFormat format = DateFormat.getDateInstance();
							text = format.format(value);
						} else if (this.dataType == Variant.TIMESTAMP) {
							DateFormat format = DateFormat.getDateTimeInstance();
							text = format.format(value);
						} else {
							text = value.toString();
						}
					}
					this.textField.setText(text);
				} else if (this.editor == this.checkBox) {
					this.checkBox.setSelected(value == null ? false : ((Boolean) value).booleanValue());
				} else if (this.editor == this.comboBox) {
					this.comboBox.setSelectedItem(value);
				}
			} finally {
				this.ignoreActions = false;
			}
		}

		/**
		 * @see javax.swing.CellEditor#isCellEditable(java.util.EventObject)
		 */
		@Override
        public boolean isCellEditable(EventObject anEvent) {
			return isEditable();
		}

		/***************************************************************************************************************
		 * Устанавливает признак редактируемости таблицы
		 * 
		 * @param theEditable true to allow edit
		 **************************************************************************************************************/
		public void setEditable(boolean theEditable) {
			if (this.editable != theEditable) {
				this.editable = theEditable;
				if (!theEditable && isEditing()) cancelCellEditing();
			}
		}

		/***************************************************************************************************************
		 * Возвращает признак редактируемости всей таблицы
		 * 
		 * @return true if editable
		 **************************************************************************************************************/
		public boolean isEditable() {
			return this.editable;
		}

		/**
		 * @see javax.swing.CellEditor#shouldSelectCell(java.util.EventObject)
		 */
		@Override
        public boolean shouldSelectCell(EventObject anEvent) {
			return false;
		}

		/**
		 * @see javax.swing.CellEditor#stopCellEditing()
		 */
		@Override
        public boolean stopCellEditing() {
			boolean ret = true;
			for (int i = 0; i < this.listeners.size(); i++) {
				try {
					this.listeners.get(i).editingStopped(new ChangeEvent(this));
				} catch (Exception ex) {
					ex.printStackTrace();
					ret = false;
				}
			}
			return ret;
		}

		/**
		 * @see javax.swing.CellEditor#cancelCellEditing()
		 */
		@Override
        public void cancelCellEditing() {
			for (int i = 0; i < this.listeners.size(); i++) {
				try {
					this.listeners.get(i).editingCanceled(new ChangeEvent(this));
				} catch (Exception ex) {
					ex.printStackTrace();
				}
			}
		}

		/**
		 * @see javax.swing.CellEditor#addCellEditorListener(javax.swing.event.CellEditorListener)
		 */
		@Override
        public void addCellEditorListener(CellEditorListener l) {
			if (l != null && !this.listeners.contains(l)) {
				this.listeners.add(l);
			}
		}

		/**
		 * @see javax.swing.CellEditor#removeCellEditorListener(javax.swing.event.CellEditorListener)
		 */
		@Override
        public void removeCellEditorListener(CellEditorListener l) {
			this.listeners.remove(l);
			if (this.listeners.size() < 1) {
				if (this.editor == this.textField) {
					this.textField.removeActionListener(this.actionListener);
				} else if (this.editor == this.checkBox) {
					this.checkBox.removeActionListener(this.actionListener);
				} else if (this.editor == this.comboBox) {
					this.comboBox.removeActionListener(this.actionListener);
				}
			}
		}

	}
}
